package main

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"time"
)

type SliceFile struct {
	Hash string `json:"hash"`
	Size int    `json:"size"`
	Name string `json:"name"`
}

type CheckObjForm struct {
	Hash      string `json:"hash"`
	Code      int    `json:"code"`
	LoseFiles []int  `json:"lose_files"`
}

var (
	fileMap map[string]*SliceFile
)

func init() {
	fileMap = make(map[string]*SliceFile)
}

func main() {
	http.HandleFunc("/startSendFile", startSendFile)
	http.HandleFunc("/sendBlob", sendBlob)
	http.HandleFunc("/setFileSize", setFileSize)
	http.HandleFunc("/mergerFile", mergerFile)

	err := http.ListenAndServe(":9999", nil)
	if err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
}
func checkCacheDir() {
	_, err := os.Stat("./cache")
	if err != nil {
		if !os.IsExist(err) {
			err := os.Mkdir("./cache", 777)
			if err != nil {
				fmt.Println("Create dir error:", err)
				os.Exit(-1)
			}
		}
	}
}

//创建文件夹，并准备接受
func startSendFile(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")             //允许访问所有域
	w.Header().Add("Access-Control-Allow-Headers", "Content-Type") //header的类型
	//w.Header().Set("content-type", "application/json")             //返回数据格式是json

	checkCacheDir()
	hash := r.URL.Query()["hash"]
	path := fmt.Sprintf("./cache/%s", hash[0])
	err := os.Mkdir(path, 777)
	time.Sleep(time.Second * 1)
	if err != nil {
		fmt.Fprint(w, 201)
		return
	}
	fmt.Fprint(w, 200)
}

//设置文件基本信息
func setFileSize(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")             //允许访问所有域
	w.Header().Add("Access-Control-Allow-Headers", "Content-Type") //header的类型
	//w.Header().Set("content-type", "application/json")             //返回数据格式是json
	hash := r.URL.Query()["hash"][0]
	if _, ok := fileMap[hash]; ok {
		fmt.Fprint(w, 200)
		return
	}
	name := r.URL.Query()["name"][0]
	size, _ := strconv.Atoi(r.URL.Query()["size"][0])
	sliceFile := &SliceFile{
		Hash: hash,
		Size: size,
		Name: name,
	}
	fileMap[hash] = sliceFile
	fmt.Fprint(w, 200)
}

//处理发送文件请求
func sendBlob(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")             //允许访问所有域
	w.Header().Add("Access-Control-Allow-Headers", "Content-Type") //header的类型
	//w.Header().Set("content-type", "application/json")             //返回数据格式是json
	file, _, err := r.FormFile("blob")
	//获取前端参数值
	fileHash := r.URL.Query()["file_hash"][0]
	hash := r.URL.Query()["hash"][0]   //前端校验值
	index := r.URL.Query()["index"][0] //文件序号
	//准备文件
	if err != nil {
		fmt.Println("从前端获取文件错误", err)
		fmt.Fprint(w, 500)
		return
	}
	file.Close()
	fileBuffer, err := ioutil.ReadAll(file)
	if err != nil {
		fmt.Println("读取前端文件错误")
		fmt.Fprint(w, 500)
		return
	}
	/*MD5校验文件完整性*/
	check := md5.New()
	check.Write(fileBuffer)
	md5Str := hex.EncodeToString(check.Sum(nil))
	//与前端的文件MD5比较
	if md5Str == hash {
		destFile, err := os.Create("./cache/" + fileHash + "/" + index)
		if err != nil {
			fmt.Fprint(w, 500)
			return
		}
		defer destFile.Close()
		_, err = destFile.Write(fileBuffer)
		if err != nil {
			fmt.Fprint(w, 500)
			return
		}
		fmt.Fprint(w, 201) //文件完整,请求继续传输
	} else {
		fmt.Fprint(w, 300) //文件不完整，请求前端重新传输
		return
	}

}

//检查项目完整性
func checkObj(hash string) *CheckObjForm {

	var size int
	if file, ok := fileMap[hash]; ok {
		size = file.Size
	} else {
		fmt.Println("没有此文件")
		return nil
	}

	files, err := ioutil.ReadDir(fmt.Sprintf("./cache/%s", hash))
	if err != nil {
		fmt.Println(err)
		return nil
	}
	checkObjForm := &CheckObjForm{
		Hash:      hash,
		Code:      200,
		LoseFiles: make([]int, 0, size),
	}
	/*检查文件-start*/
	fileList := make(map[int]int)
	for i, item := range files {
		index, _ := strconv.Atoi(item.Name())
		fileList[index] = i
	}
	for i := 0; i < size; i++ {
		if _, ok := fileList[i]; !ok {
			checkObjForm.LoseFiles = append(checkObjForm.LoseFiles, i)
		}
	}

	/*检查文件-end*/
	if len(checkObjForm.LoseFiles) != 0 {
		checkObjForm.Code = 300
	}

	return checkObjForm

}

//合并文件, 并告知发送状态
func mergerFile(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")             //允许访问所有域
	w.Header().Add("Access-Control-Allow-Headers", "Content-Type") //header的类型
	w.Header().Set("content-type", "application/json")             //返回数据格式是json
	hash := r.URL.Query()["hash"][0]
	checkObjForm := checkObj(hash)
	if checkObjForm.Code != 200 {
		fmt.Fprint(w, checkObjForm.Code)
		fmt.Println("文件上传不完整")
		return
	}

	fmt.Println(time.Now().Format("2006-01-02 03:04:05"), ":", hash)
	name := ""
	if fileInfo, ok := fileMap[hash]; ok {
		name = fileInfo.Name
	} else {
		fmt.Fprint(w, 500)
		fmt.Println("没有此文件")
		return
	}
	path := fmt.Sprintf("./cache/%s", hash)
	files, _ := ioutil.ReadDir(path)
	fmt.Println("合并文件", name)

	//查看文件是否存在
	_, err := os.Stat(path + "/" + fileMap[hash].Name)
	if err == nil {
		fmt.Println("文件已存在")
		fmt.Fprint(w, 201)
		return
	}

	//创建最后的文件
	completeFile, err := os.Create(path + "/" + fileMap[hash].Name)

	if err != nil {
		fmt.Println("创建文件错误", err)
		fmt.Fprint(w, 500)
		return
	}
	defer completeFile.Close()

	for _, f := range files {
		if f.Name() == ".DS_Store" {
			continue
		}
		filePath := path + "/" + f.Name()
		fileBuffer, err := ioutil.ReadFile(filePath)
		if err != nil {
			fmt.Println("读取文件时错误", err)
			fmt.Fprint(w, 500)
			return
		}
		_, err = completeFile.Write(fileBuffer)

		if err != nil {
			fmt.Println("写入文件时错误", err)
			fmt.Fprint(w, 500)
			return
		}
		err = os.Remove(filePath)
		if err != nil {
			fmt.Println("删除文件时错误", err)
			fmt.Fprint(w, 500)
			return
		}
	}
	fmt.Fprint(w, 200)
}
